﻿using System;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;

namespace Renci.Data.Interop.Serialization
{
    internal class PropertyAttributeSerializationInfo : PropertySerializationInfo
    {
        public PropertyAttributeSerializationInfo(PropertyInfo property)
            : base(property)
        {
        }

        public bool IsArrayItem { get; set; }

        public bool IsElement { get; set; }

        public bool IsAttribute { get; set; }

        public bool IsAnyElement { get; set; }

        public bool IsAnyAttribute { get; set; }

        public XmlQualifiedName Name { get; set; }

        public string DataType { get; set; }

        public XmlSchemaForm Form { get; set; }

        public bool IsNullable { get; set; }

        public int Order { get; set; }

        private Type _type;

        public Type Type
        {
            get
            {
                if (this._type == null)
                {
                    this._type = this.Property.PropertyType;
                }

                return this._type;
            }
            set
            {
                this._type = value;
            }
        }

        public override void SetPropertyValue(object obj, object value)
        {
            if (this.ChoiceField != null)
            {
                //  TODO:   Extract it into seperate class and set this property only when value string or enum.
                base.SetChoicePropertyValue(obj, this.Name);
            }

            //  Set property value
            base.SetPropertyValue(obj, value);
        }

        internal object GetComplexValue(System.Xml.XmlReader reader)
        {
            var typeName = reader.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance");
            var isNullable = bool.Parse(reader.GetAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance") ?? bool.FalseString);

            if (this.Type.IsPrimitive ||
                this.Type == typeof(string))
            {
                return reader.ReadElementContentAs(this.Type, null);
            }
            else if (this.Type.IsEnum)
            {
                return this.GetEnumValue(this.Property, reader.ReadElementContentAsString());
            }
            else if (this.IsNullable && isNullable)
            {
                //var propertyValue = elementInfo.Property.GetValue(obj, null);
                //SetPropertyValue(obj, elementInfo.Property, elementValue);

                //  TODO:   Do something here to handle nullable types, specificlly Value types

                if (this.Type.IsPrimitive)
                {
                    //  TODO:   Set Specified attribute to false
                    throw new NotImplementedException("Nullable types not yet implemented.");
                }

                //  Do nothing for reference types
                return null;
            }
            else if (!string.IsNullOrEmpty(typeName))
            {
                string[] name = typeName.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
                var qn = new XmlQualifiedName(name[1], reader.LookupNamespace(name[0]));

                var elementType = (from attr in InteropSerializer.TypeAttributes
                                   where
                                  attr.Attribute.Namespace == qn.Namespace
                                  && attr.Attribute.TypeName == qn.Name
                                   select attr.Type).SingleOrDefault();

                if (elementType != null)
                {
                    return GetElementValue(reader, elementType);
                }
                else
                {
                    //  TODO:   Do something when type not found and cannot be read from XML
                }
                throw new NotImplementedException(string.Format("Cannot handle unknow type: {0}", typeName));
            }
            else if (this.Type == typeof(object))
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(reader.ReadOuterXml());
                var xmlNodes = (from node in doc.FirstChild.Attributes.Cast<XmlNode>()
                                select node).Union(from node in doc.FirstChild.ChildNodes.Cast<XmlNode>()
                                                   select node);
                return xmlNodes.ToList().ToArray();
            }
            else if (this.Type == typeof(System.Xml.XmlQualifiedName))
            {
                return reader.ReadElementContentAs(typeof(System.Xml.XmlQualifiedName), null);
            }
            else if (typeof(System.Xml.XmlElement) == this.Type)
            {
                var doc = new XmlDocument();
                var value = reader.ReadOuterXml();
                doc.LoadXml(value);
                return doc.DocumentElement;
            }
            else
            {
                return GetElementValue(reader, this.Type);
            }
        }

        private static object GetElementValue(System.Xml.XmlReader reader, Type type)
        {
            var elementValue = Activator.CreateInstance(InteropSerializer.GetElementType(type));
            InteropSerializer.ReadXml(reader.ReadSubtree(), elementValue);
            return elementValue;
        }
    }
}
